﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;

namespace QQ2564874169.RelationalSql
{
    public interface IDbExecute : IDisposable
    {
        event EventHandler<DbExecuteBeforeEventArgs> Before;
        event EventHandler<DbExecuteAfterEventArgs> After;
        event EventHandler<DbExecuteErrorEventArgs> Error;

        IDictionary Data { get; }
        bool InTransaction { get; }
        int Insert<T>(T model, int? timeout = null);
        int Delete<T>(T model, int? timeout = null);
        int Update<T>(T setModel, T whereModel, int? timeout = null);
        int SetNull<T>(T newModel, T whereModel, int? timeout = null);
        int Proc<T>(T model, int? timeout = null);
        ITransactionScope BeginTransaction();
    }

    public abstract class DbExecute : IDbExecute
    {
        public static event Action<DbExecute> Created;
        public event EventHandler<DbExecuteBeforeEventArgs> Before;
        public event EventHandler<DbExecuteAfterEventArgs> After;
        public event EventHandler<DbExecuteErrorEventArgs> Error;
        public IDictionary Data { get; private set; }
        public bool InTransaction => _sql.InTransaction;

        private ISql _sql;

        protected DbExecute(ISql sql)
        {
            _sql = sql;
            Data = new Hashtable();
            Created?.Invoke(this);
        }

        public virtual void Dispose()
        {
            Data?.Clear();
            Data = null;
            Before = null;
            After = null;
            Error = null;
            _sql = null;
        }

        protected virtual void OnBefore(DbExecuteBeforeEventArgs e)
        {
            Before?.Invoke(this, e);
        }

        protected virtual void OnAfter(DbExecuteAfterEventArgs e)
        {
            After?.Invoke(this, e);
        }

        protected virtual void OnError(DbExecuteErrorEventArgs e)
        {
            Error?.Invoke(this, e);
        }

        protected static IDictionary<string, object> ParseModel(object model, Type type = null)
        {
            var map = new Dictionary<string, object>();
            if (model == null) return map;
            var t = type ?? model.GetType();
            var ps = t.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);

            foreach (var p in ps)
            {
                var v = p.GetValue(model);
                if (v != null)
                {
                    map.Add(p.Name, v);
                }
            }
            return map;
        }

        protected abstract ExecuteParam OnCreateSql(DbExecuteOperation operation, Type tableType, object model);

        private int OnExecute(DbExecuteBeforeEventArgs before, int? timeout = null)
        {
            try
            {
                OnBefore(before);
                if (before.Result.HasValue == false)
                {
                    var context = OnCreateSql(before.Operation, before.TableType, before.Model);
                    before.Result = before.Sql.Execute(context.Command, context.Parameters, context.IsProc, timeout);
                }
                return before.Result.Value;
            }
            catch (Exception ex)
            {
                var e = new DbExecuteErrorEventArgs(before, ex);
                OnError(e);
                if (e.Result.HasValue)
                {
                    return e.Result.Value;
                }
                throw;
            }
            finally
            {
                OnAfter(new DbExecuteAfterEventArgs(before));
            }
        }

        public int Insert<T>(T model, int? timeout = null)
        {
            var before = new DbExecuteBeforeEventArgs(_sql)
            {
                Operation = DbExecuteOperation.Insert,
                TableType = typeof(T),
                Model = model
            };
            return OnExecute(before, timeout);
        }

        public int Delete<T>(T model, int? timeout = null)
        {
            var before = new DbExecuteBeforeEventArgs(_sql)
            {
                Operation = DbExecuteOperation.Delete,
                TableType = typeof(T),
                Model = model
            };
            return OnExecute(before, timeout);
        }

        public int Update<T>(T setModel, T whereModel, int? timeout = null)
        {
            var before = new DbExecuteBeforeEventArgs(_sql)
            {
                Operation = DbExecuteOperation.Update,
                TableType = typeof(T),
                Model = new DbUpdateEventArgs(whereModel, setModel, false)
            };
            return OnExecute(before, timeout);
        }

        public int SetNull<T>(T setModel, T whereModel, int? timeout = null)
        {
            var before = new DbExecuteBeforeEventArgs(_sql)
            {
                Operation = DbExecuteOperation.SetNull,
                TableType = typeof(T),
                Model = new DbUpdateEventArgs(whereModel, setModel, true)
            };
            return OnExecute(before, timeout);
        }

        public int Proc<T>(T model, int? timeout = null)
        {
            var before = new DbExecuteBeforeEventArgs(_sql)
            {
                Operation = DbExecuteOperation.Proc,
                TableType = typeof(T),
                Model = model
            };
            return OnExecute(before, timeout);
        }

        public virtual ITransactionScope BeginTransaction()
        {
            return _sql.BeginTransaction();
        }
    }
}
